//
//  CXMLNode.m
//  VmixXml
//
//  Created by Lei Pan on 5/28/10.
//  Copyright 2010 VMIX Media. All rights reserved.
//

#import "CXMLNode.h"

#import "CXMLNode_PrivateExtensions.h"
#import "CXMLDocument.h"
#import "CXMLElement.h"

#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>

@implementation CXMLNode

- (void)dealloc
{
	if (_node)
	{
		if (_node->_private == self)
			_node->_private = NULL;
		_node = NULL;
	}
	//
	[super dealloc];
}

- (id)copyWithZone:(NSZone *)zone;
{
	xmlNodePtr theNewNode = xmlCopyNode(_node, 1);
	CXMLNode *theNode = [[[self class] alloc] initWithLibXMLNode:theNewNode];
	theNewNode->_private = theNode;
	return(theNode);
}

#pragma mark -

- (CXMLNodeKind)kind
{
	NSAssert(_node != NULL, @"CXMLNode does not have attached libxml2 _node.");
	return(_node->type); // TODO this isn't 100% accurate!
}

- (NSString *)name
{
	NSAssert(_node != NULL, @"CXMLNode does not have attached libxml2 _node.");
	// TODO use xmlCheckUTF8 to check name
	if (_node->name == NULL)
		return(NULL);
	else
		return([NSString stringWithUTF8String:(const char *)_node->name]);
}

- (NSString *)stringValue
{
	NSAssert(_node != NULL, @"CXMLNode does not have attached libxml2 _node.");
	xmlChar *theXMLString;
	BOOL theFreeReminderFlag = NO;
	if (_node->type == XML_TEXT_NODE || _node->type == XML_CDATA_SECTION_NODE) 
		theXMLString = _node->content;
	else
	{
		theXMLString = xmlNodeListGetString(_node->doc, _node->children, YES);
		theFreeReminderFlag = YES;
	}
	
	NSString *theStringValue = NULL;
	if (theXMLString != NULL)
	{
		theStringValue = [NSString stringWithUTF8String:(const char *)theXMLString];
		if (theFreeReminderFlag == YES)
		{
			xmlFree(theXMLString);
		}
	}
	
	return(theStringValue);
}

- (NSUInteger)index
{
	NSAssert(_node != NULL, @"CXMLNode does not have attached libxml2 _node.");
	
	xmlNodePtr theCurrentNode = _node->prev;
	NSUInteger N;
	for (N = 0; theCurrentNode != NULL; ++N, theCurrentNode = theCurrentNode->prev)
		;
	return(N);
}

- (NSUInteger)level
{
	NSAssert(_node != NULL, @"CXMLNode does not have attached libxml2 _node.");
	
	xmlNodePtr theCurrentNode = _node->parent;
	NSUInteger N;
	for (N = 0; theCurrentNode != NULL; ++N, theCurrentNode = theCurrentNode->parent)
		;
	return(N);
}

- (CXMLDocument *)rootDocument
{
	NSAssert(_node != NULL, @"CXMLNode does not have attached libxml2 _node.");
	
	return(_node->doc->_private);
}

- (CXMLNode *)parent
{
	NSAssert(_node != NULL, @"CXMLNode does not have attached libxml2 _node.");
	
	if (_node->parent == NULL)
		return(NULL);
	else
		return (_node->parent->_private);
}

- (NSUInteger)childCount
{
	NSAssert(_node != NULL, @"CXMLNode does not have attached libxml2 _node.");
	
	xmlNodePtr theCurrentNode = _node->children;
	NSUInteger N;
	for (N = 0; theCurrentNode != NULL; ++N, theCurrentNode = theCurrentNode->next)
		;
	return(N);
}

- (NSArray *)children
{
	NSAssert(_node != NULL, @"CXMLNode does not have attached libxml2 _node.");
	
	NSMutableArray *theChildren = [NSMutableArray array];
	xmlNodePtr theCurrentNode = _node->children;
	while (theCurrentNode != NULL)
	{
		CXMLNode *theNode = [CXMLNode nodeWithLibXMLNode:theCurrentNode];
		[theChildren addObject:theNode];
		theCurrentNode = theCurrentNode->next;
	}
	return(theChildren);      
}

- (CXMLNode *)childAtIndex:(NSUInteger)index
{
	NSAssert(_node != NULL, @"CXMLNode does not have attached libxml2 _node.");
	
	xmlNodePtr theCurrentNode = _node->children;
	NSUInteger N;
	for (N = 0; theCurrentNode != NULL && N != index; ++N, theCurrentNode = theCurrentNode->next)
		;
	if (theCurrentNode)
		return([CXMLNode nodeWithLibXMLNode:theCurrentNode]);
	return(NULL);
}

- (CXMLNode *)previousSibling
{
	NSAssert(_node != NULL, @"CXMLNode does not have attached libxml2 _node.");
	
	if (_node->prev == NULL)
		return(NULL);
	else
		return([CXMLNode nodeWithLibXMLNode:_node->prev]);
}

- (CXMLNode *)nextSibling
{
	NSAssert(_node != NULL, @"CXMLNode does not have attached libxml2 _node.");
	
	if (_node->next == NULL)
		return(NULL);
	else
		return([CXMLNode nodeWithLibXMLNode:_node->next]);
}

//- (CXMLNode *)previousNode;
//- (CXMLNode *)nextNode;
//- (NSString *)XPath;

- (NSString *)localName
{
	NSAssert(_node != NULL, @"CXMLNode does not have attached libxml2 _node.");
	// TODO use xmlCheckUTF8 to check name
	if (_node->name == NULL)
		return(NULL);
	else
		return([NSString stringWithUTF8String:(const char *)_node->name]); // TODO this is the same as name. What's up with thaat?
}

- (NSString *)prefix
{
	if (_node->ns)
		return([NSString stringWithUTF8String:(const char *)_node->ns->prefix]);
	else
		return(NULL);
}

- (NSString *)URI
{
	if (_node->ns)
		return([NSString stringWithUTF8String:(const char *)_node->ns->href]);
	else
		return(NULL);
}

//+ (NSString *)localNameForName:(NSString *)name;
//+ (NSString *)prefixForName:(NSString *)name;
//+ (CXMLNode *)predefinedNamespaceForPrefix:(NSString *)name;

- (NSString *)description
{
	NSAssert(_node != NULL, @"CXMLNode does not have attached libxml2 _node.");
	
	return([NSString stringWithFormat:@"<%@ %p [%p] %@ %@>", NSStringFromClass([self class]), self, self->_node, [self name], [self XMLStringWithOptions:0]]);
}

- (NSString *)XMLString
{
	return([self XMLStringWithOptions:0]);
}

- (NSString*)_XMLStringWithOptions:(NSUInteger)options appendingToString:(NSMutableString*)str
{
#pragma unused (options)
	
	id value = NULL;
	switch([self kind])
	{
		case CXMLAttributeKind:
			value = [NSMutableString stringWithString:[self stringValue]];
			[value replaceOccurrencesOfString:@"\"" withString:@"&quot;" options:0 range:NSMakeRange(0, [value length])];
			[str appendFormat:@" %@=\"%@\"", [self name], value];
			break;
		case CXMLTextKind:
			[str appendString:[self stringValue]];
			break;
		case XML_COMMENT_NODE:
		case XML_CDATA_SECTION_NODE:
			// TODO: NSXML does not have XML_CDATA_SECTION_NODE correspondent.
			break;
		default:
			NSAssert1(NO, @"TODO not implemented type (%d).",  [self kind]);
	}
	return str;
}

- (NSString *)XMLStringWithOptions:(NSUInteger)options
{
	return [self _XMLStringWithOptions:options appendingToString:[NSMutableString string]];
}
//- (NSString *)canonicalXMLStringPreservingComments:(BOOL)comments;

- (NSArray *)nodesForXPath:(NSString *)xpath error:(NSError **)error
{
#pragma unused (error)
	
	NSAssert(_node != NULL, @"CXMLNode does not have attached libxml2 _node.");
	
	NSArray *theResult = NULL;
	
	xmlXPathContextPtr theXPathContext = xmlXPathNewContext(_node->doc);
	theXPathContext->node = _node;
	
	// TODO considering putting xmlChar <-> UTF8 into a NSString category
	xmlXPathObjectPtr theXPathObject = xmlXPathEvalExpression((const xmlChar *)[xpath UTF8String], theXPathContext);
	if (xmlXPathNodeSetIsEmpty(theXPathObject->nodesetval))
		theResult = [NSArray array]; // TODO better to return NULL?
	else
	{
		NSMutableArray *theArray = [NSMutableArray array];
		int N;
		for (N = 0; N < theXPathObject->nodesetval->nodeNr; N++)
		{
			xmlNodePtr theNode = theXPathObject->nodesetval->nodeTab[N];
			[theArray addObject:[CXMLNode nodeWithLibXMLNode:theNode]];
		}
		
		theResult = theArray;
	}
	
	xmlXPathFreeObject(theXPathObject);
	xmlXPathFreeContext(theXPathContext);
	return(theResult);
}

- (NSString *)getElementValue:(NSString *)xpath error:(NSError **)error
{
	#pragma unused (error)
	
	NSAssert(_node != NULL, @"CXMLNode does not have attached libxml2 _node.");
	
	NSString *theResult = @"";
	
	xmlXPathContextPtr theXPathContext = xmlXPathNewContext(_node->doc);
	theXPathContext->node = _node;
	
	// TODO considering putting xmlChar <-> UTF8 into a NSString category
	xmlXPathObjectPtr theXPathObject = xmlXPathEvalExpression((const xmlChar *)[xpath UTF8String], theXPathContext);
	if (xmlXPathNodeSetIsEmpty(theXPathObject->nodesetval))
        //theResult = @"Element not found"; // TODO better to return NULL? - ANSWER: YES!!!
        theResult = NULL;
	else
	{
		
		xmlNodePtr theNode = theXPathObject->nodesetval->nodeTab[0];
		theResult = [[CXMLNode nodeWithLibXMLNode:theNode] stringValue];
	}
	
	xmlXPathFreeObject(theXPathObject);
	xmlXPathFreeContext(theXPathContext);
	return(theResult);
}


//- (NSArray *)objectsForXQuery:(NSString *)xquery constants:(NSDictionary *)constants error:(NSError **)error;
//- (NSArray *)objectsForXQuery:(NSString *)xquery error:(NSError **)error;

@end
